﻿Option Strict On
Imports System.Runtime.Serialization
<Serializable()>
Public Class MetaDataTable
    Inherits ExtendedDataTable
    'Implements IDisposable

#Region " Constructor "
    Public Sub New()
        MyBase.New()
    End Sub
    ''' <summary>
    ''' Creates a new MetaDataTable by initializing it's adapter.
    ''' Defines what data will be load into the DataTable and how it will
    ''' be managed.
    ''' </summary>
    ''' <param name="adapter"></param>
    ''' <remarks>The SqlDataAdapter should have a valid SelectCommand and include a rowversion  (alias timestamp) field</remarks>
    Public Sub New(ByVal adapter As SqlDataAdapter)
        MyBase.New(adapter)
    End Sub
    ''' <summary>
    ''' Creates a new MetaDataTable by initializing it's adapter.
    ''' Defines what data will be load into the DataTable and how it will
    ''' be managed.
    ''' </summary>
    ''' <param name="adapter"></param>
    ''' <param name="command">MetaCommand (select)</param>
    ''' <remarks>The "select" statement in the command parameter must include a timestamp (alias rowversion) field</remarks>
    Public Sub New(ByVal adapter As SqlDataAdapter, ByVal command As String)
        MyBase.New(adapter, command)
    End Sub
    Public Sub New(ByVal tableName As String)
        MyBase.New(tableName)
    End Sub
    Public Sub New(ByVal tableName As String, ByVal tableNamespace As String)
        MyBase.New(tableName, tableNamespace)
    End Sub
    Protected Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
        MyBase.New(info, context)
    End Sub
#End Region

#Region " Field "
    'Private sqlAdapter As New SqlDataAdapter()
    Public Command As SqlCommand = Base.Connection.Command
#End Region
#Region " Property Field "
    Private _queryName As String
    Private _metaCommand As MetaQuery = New MetaQuery
    Private _metaData As DataTable
    Private _metaParams As DataTable
#End Region
#Region " Event "
    Public Event OnRowUpdating(ByVal sender As Object, ByVal e As SqlRowUpdatingEventArgs)
    Public Event OnRowUpdated(ByVal sender As Object, ByVal e As SqlRowUpdatedEventArgs)
#End Region
#Region " Property "
    ''' <summary>
    ''' Shortcut to test if we have Lookup to proceed by the consumer (Ex.: ExtendedGrid)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property HasLookup() As Boolean
        Get
            Return Me.MetaCommand.HasLookup
        End Get
    End Property
    ''' <summary>
    ''' Shortcut to test if we have a guid Field
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GUIDField() As String
        Get
            Return Me.MetaCommand.GUIDField
        End Get
    End Property
    ''' <summary>
    ''' Shortcut to test if we have LOV to proceed by the consumer (Ex.: ExtendedGrid)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property HasLov() As Boolean
        Get
            Return Me.MetaCommand.HasLov
        End Get
    End Property
    ''' <summary>
    ''' Lookup list that will be process by the main consumer
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ListOfLookup() As Dictionary(Of String, Guid)
        Get
            Return Me.MetaCommand.ListOfLookup
        End Get
    End Property
    ''' <summary>
    ''' Lookup list that will be process by the main consumer
    ''' They are either LOV's or Translates
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ListOfVisibleLookup() As Dictionary(Of String, Guid)
        Get
            Return Me.MetaCommand.ListOfVisibleLookup
        End Get
    End Property

    ''' <summary>
    ''' LOV list that will be process by the main consumer
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ListOfLov() As Dictionary(Of String, Guid)
        Get
            Return Me.MetaCommand.ListOfLov
        End Get
    End Property
    ''' <summary>
    ''' LOV list that will be process by the main consumer
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ListOfVisibleLov() As Dictionary(Of String, Guid)
        Get
            Return Me.MetaCommand.ListOfVisibleLov
        End Get
    End Property
    ''' <summary>
    ''' All the details of a Metacommand. SQL etc...
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property MetaCommand() As MetaQuery
        Get
            Return _metaCommand
        End Get
        Set(ByVal value As MetaQuery)
            If value IsNot Nothing Then
                _metaCommand = value
                'If we assign the object _metaCommand from the consumer we must assign the _queryname
                _queryName = _metaCommand.QueryName
                QueryCD = _metaCommand.MetaQuerieCD
                QueryControlID = _metaCommand.MetaControlID
            End If
        End Set
    End Property
    ''' <summary>
    ''' This Datatable contains the metacolumn properties to be applied by the consumer (Ex.: ExtendedGrid)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property MetaData() As DataTable
        Get
            If _metaData Is Nothing Then
                Me.MetaCommand.FillMetaColumns()
                _metaData = Me.MetaCommand.MetaColumns
            End If
            Return _metaData
        End Get
        Set(ByVal value As DataTable)
            _metaData = value
        End Set
    End Property
    ''' <summary>
    ''' This Datatable contains the Param properties 
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property MetaParams() As DataTable
        Get
            If _metaParams Is Nothing Then
                Me.MetaCommand.FillMetaParams()
                _metaParams = Me.MetaCommand.MetaParams
            End If
            Return _metaParams
        End Get
        Set(ByVal value As DataTable)
            _metaParams = value
        End Set
    End Property
    ''' <summary>
    ''' Contain the QueryName of the MetaCommand/Metaqueries
    ''' TestPoint: Define your queryName here to get the data
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property QueryName() As String
        Get
            Return _queryName
        End Get
        Set(ByVal value As String)
            _queryName = value
            Me.TableName = _queryName
        End Set
    End Property

    ''' <summary>
    ''' Implemented , to be developper stressed
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property QueryCD As String = ""

    ''' <summary>
    ''' Implemented , to be developper stressed
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property QueryControlID As Guid? = System.Guid.Empty

    ''' <summary>
    ''' Implemented , to be developper stressed
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property QueryID As Guid? = System.Guid.Empty


#End Region
#Region " Method "
#Region "Sub"
    ''' <summary>
    ''' This function has the purpose of returning a datarow from the dataTable loaded in memory using the GUID as key filter.
    ''' </summary>
    ''' <param name="id">System.Guid</param>
    ''' <remarks>The architectural purpose of this function is to permit a call from a consumer to recuperate a datarow object.
    ''' That datarow is used mostly in systembox to serve has the datasource for a BLL entity for binding to control that are not directly bindeable to ADO.datatable.
    ''' The usage of the Microsoft Propertygrid is an good example and primary usage of GetFromtable
    '''  </remarks>

    Public Function GetFromTable(ByVal id As Guid) As DataRow
        Dim _dataRow As DataRow = Rows.Find(id)
        'Exception are handled by Exceptionhandling Class
        Return _dataRow
    End Function
    Public Sub GetFromDB(ByVal id As Guid)
        Throw New NotImplementedException("GetFromDB")
    End Sub
    Public Sub GetAll()
        Throw New NotImplementedException("GetAll")
    End Sub
    ''' <summary>
    ''' Prepare the CUD comand for the adapter then update and Accepts Changes.
    ''' By design we go get the queries for CUD and Add the handler on the adapter, so we go get de RecordVersion 
    ''' before it was done in the FillBusinessApplicationData
    ''' You will neet to have a OnRowUpdating in your PL if you need to call SP for Sequence
    ''' </summary>
    ''' <param name="continueUpdateOnError">True - Will process all row event if an error. False - Stop the update on the first row on error </param>
    ''' <returns></returns>
    ''' <remarks>
    ''' It is very important to verify that the newrecord function is called to initialise the key field of the record.
    ''' It would be possible in extreme case to overrides that function by initializing the key field of a record in the overriden menber of a factory class in the event initnewmasterrow or initnewMasterRow</remarks>
    Public Function ApplyChanges(Optional ByVal continueUpdateOnError As Boolean = True, Optional ByVal dtTable As DataTable = Nothing, Optional reload As Boolean = True) As Integer
        Dim nbrec As Integer = 0
        'Testpoint : Applychanges and METACOMMANDCLAS_CUD
        If Me.MetaCommand.CommandClassID = Base.IdRepository.METALIST_METACOMMANDCLASS_CUD Then
            'No need to go get the MetaQuery from the DB. Query for CUD is the same. 
            Me.BuildCUDCommands(Me.MetaCommand)
        Else
            AssignAdapterCommandForCUD()
        End If
        'Ajouter les AddHandler seulement si on modifie on peut faire du CUD.
        If Me.Adapter.UpdateCommand IsNot Nothing _
            OrElse Me.Adapter.DeleteCommand IsNot Nothing _
            OrElse Me.Adapter.InsertCommand IsNot Nothing Then
            AddHandler Me.Adapter.RowUpdated, AddressOf RowUpdated
            AddHandler Me.Adapter.RowUpdating, AddressOf RowUpdating
        End If
        '
        Me.Adapter.ContinueUpdateOnError = continueUpdateOnError
        If dtTable IsNot Nothing Then
            nbrec = Me.Adapter.Update(dtTable)
        Else
            nbrec = Me.Adapter.Update(Me)
        End If
        If nbrec > 0 Then

            Me.AcceptChanges() ' this line handle the add new and prevent to have two new lines and an errore of duplicate key

            ' the reload prevent to raise a concurrency issue on the same session and updating twice the same record.
            ' should change the bsp_... to return the rowversion then change it , adjust the rowstate etc.
            If reload = True Then
                Me.Adapter.Fill(Me)
            End If

        End If
        '
        'Ajouter les AddHandler seulement si on modifie on peut faire du CUD.
        If Me.Adapter.UpdateCommand IsNot Nothing _
            OrElse Me.Adapter.DeleteCommand IsNot Nothing _
            OrElse Me.Adapter.InsertCommand IsNot Nothing Then
            RemoveHandler Me.Adapter.RowUpdated, AddressOf RowUpdated
            RemoveHandler Me.Adapter.RowUpdating, AddressOf RowUpdating
        End If

        Return nbrec
    End Function


    ''' <summary>
    ''' Fill local field class MetaCommand using the QueryName
    ''' </summary>
    ''' <remarks>Propertie QueryName must be set</remarks>
    <ObsoleteAttribute("This Sub is obsolete. Use thoer signature with speicfied parameter , name or ID.", False)>
    Public Sub GetMetaCommand()
        Me._metaCommand = MetaQuery.GetMetaQueryByName(Me.QueryName)
    End Sub

    ''' <summary>
    ''' Gets the metacommnad object using MetaqueryName
    ''' </summary>
    ''' <param name="metaQueryName"></param>
    ''' <remarks></remarks>
    Public Sub GetMetaCommand(ByVal metaQueryName As String)
        Me._metaCommand = MetaQuery.GetMetaQueryByName(metaQueryName)

    End Sub



    ''' <summary>
    ''' Gets the metacommnad object using MetaqueryID
    ''' </summary>
    ''' <param name="metaQueryID"></param>
    ''' <remarks>This will be the final Method and it will be synchronized with the IdRepository</remarks>
    Public Sub GetMetaCommand(ByVal metaQueryID As Guid)
        Me._metaCommand = MetaQuery.GetMetaQueryById(metaQueryID)

    End Sub


    ''' <summary>
    ''' Main method of the class. Load all then info. needed.
    ''' </summary>
    ''' <param name="queryName"></param>
    ''' <remarks></remarks>
    Public Sub FillBusinessApplicationData(ByVal queryName As String)
        Me.QueryName = queryName
        Me.FillBusinessApplicationData()
    End Sub

    Public Sub FillResultQuery(ByVal SqlCmd As SqlCommand)

        Command.CommandText = Me.MetaCommand.SQLStatement
        Me.Adapter.SelectCommand = SqlCmd
        Me.Adapter.Fill(Me)
        'Construit nos listes de LOV / TR pour que nos grilles fonctionne comme il le faut. - AL 2013-03-19
        Me.MetaCommand.GetLOV_LKP()
    End Sub
    ''' <summary>
    ''' Main method of the class. Load all the info. needed. When queryName is assign.
    ''' 
    ''' If you want to do CUD with one query: 
    ''' 1- Make sure your ClassCommand is of Type SELECT For CUD 
    ''' 2- You have a TStamp field or RecordVersion in your SELECT Statement
    ''' 3- Crtd_DT, Crtd_UId, LUpd_UId, LUpd_DT are in your SELECT Statement 
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub FillBusinessApplicationData()
        'Here we go get the MetaCommand if the consumer did not set it.
        'The consumer might have an extra filter to apply to the query not define in the MetaCommand

        'HACK: String.IsNullOrEmpty(Me.MetaCommand.SQLSelect) - Aleblanc 2011-02-14
        'Me.MetaCommand est tjrs different de nothing car initialiser au début... Si le SQLSelect a rien ca veux dire que la MetaCommand est pas loader
        '
        If String.IsNullOrEmpty(Me.MetaCommand.QueryName) Then
            Me.GetMetaCommand()
        End If
        If Not Me.MetaCommand Is Nothing Then
            Try
                command.CommandText = Me.MetaCommand.SQLStatement
                Me.Adapter.SelectCommand = command
                Me.Adapter.Fill(Me)
                '"CUD Ready": SELECT have a record version or TsTamp and does not have JOIN
                'If SelectCommand is "CUD Ready" then 
                'Me._businessApplicationData will be able to support CRUD 
                '
                'TestPoint: Ici on a le nom de la metaquery
                Me.TableName = Me.MetaCommand.QueryName
                If Not String.IsNullOrEmpty(Me.MetaCommand.GUIDField) Then
                    If Me.Columns.Contains(Me.MetaCommand.GUIDField) Then
                        '
                        'OutStandingIssues: Est-ce que le PrimaryKey doit être définie si la Meta est de type Read? - aleblanc 2011-04-05
                        '                   Remarque: Si on défini une PrimaryKey elle doit être unique.
                        Me.PrimaryKey = New DataColumn() {Me.Columns(Me.MetaCommand.GUIDField)}
                        Me.Columns.Item(Me.MetaCommand.GUIDField).AllowDBNull = True
                    Else
                        Throw New Exception(String.Format("GUIDFIeld {0}, Does Not Exist In Your MetaQuery Select Statement", Me.MetaCommand.GUIDField))
                    End If
                End If
                'Serait bien de pouvoir aller chercher les default values de la BD si on les définie pas et que les Champs sont Not Null Allowed - Aleblanc
                For Each defaultValue As KeyValuePair(Of String, Object) In Me.MetaCommand.DefaultParentValue
                    If Me.Columns.Contains(defaultValue.Key) Then
                        Me.Columns.Item(defaultValue.Key).DefaultValue = defaultValue.Value
                    Else
                        Throw New Exception(String.Format("Column {0}, Does Not Exist In Your MetaQuery Select Statement", defaultValue.Key))
                    End If
                Next
                '
                If Me.Columns.Contains("RecordVersion") Then
                    Me.Columns.Item("RecordVersion").AllowDBNull = True
                    Me.Columns.Item("RecordVersion").Unique = False
                End If
                '
                If Me.Columns.Contains("TStamp") Then
                    Me.Columns.Item("TStamp").AllowDBNull = True
                    Me.Columns.Item("TStamp").Unique = False
                End If
                '
                'HACK:
                'This is all the system Fields that we need to update to keep our audit
                'Even if we have trigger it seem they do not update our field
                '
                If Me.Columns.Contains("LUpd_UId") Then
                    Me.Columns.Item("LUpd_UId").DefaultValue = ActiveFramework.CommonExt.Base.AppManager.UserName()
                End If

                If Me.Columns.Contains("Crtd_UId") Then
                    Me.Columns.Item("Crtd_UId").DefaultValue = ActiveFramework.CommonExt.Base.AppManager.UserName()
                End If

                If Me.Columns.Contains("LUpd_DT") Then
                    Me.Columns.Item("LUpd_DT").DefaultValue = Date.Now
                End If

                If Me.Columns.Contains("Crtd_DT") Then
                    Me.Columns.Item("Crtd_DT").DefaultValue = Date.Now
                End If
                'aleblanc - 2011-04-11 Déplaceer dans ApplyChanges
                'If Me.MetaCommand.CommandClassID = Base.IdRepository.METALIST_METACOMMANDCLAS_CUD Then
                '    'No need to go get the MetaQuery from the DB. Query for CUD is the same. 
                '    Me.BuildCUDCommands(Me.MetaCommand)
                'Else
                '    AssignAdapterCommandForCUD()
                'End If
                ''Ajouter les AddHandler seulement si on modifie on peut faire du CUD.
                'If Me.Adapter.UpdateCommand IsNot Nothing _
                '    OrElse Me.Adapter.DeleteCommand IsNot Nothing _
                '    OrElse Me.Adapter.InsertCommand IsNot Nothing Then
                '    AddHandler Me.Adapter.RowUpdated, AddressOf RowUpdated
                '    AddHandler Me.Adapter.RowUpdating, AddressOf RowUpdating
                'End If
                Me.MetaCommand.GetLOV_LKP()
            Catch ex As Exception
                Throw New Exception(String.Format("Query error MetaDataTable.FillBusinessApplicationData(){0}{1}{0}MetaQuery: {2}", vbCrLf, ex.Message, Me.QueryName))
            End Try
        Else
            Throw New Exception(String.Format("Command is not Define: MetaDataTable.FillBusinessApplicationData(){0}MetaQuery: {1}", vbCrLf, Me.QueryName))
        End If
    End Sub
    ''' <summary>
    ''' Here we go get the child query to do Create, Update and Delete (CUD)
    ''' </summary>
    ''' <remarks>By design, Scief model for Store Proc. We use the same for Insert and Update</remarks>
    Private Sub AssignAdapterCommandForCUD()
        For Each mq As MetaQuery In Me.MetaCommand.ChildQueries
            Select Case mq.CommandClassID
                Case Base.IdRepository.METALIST_METACOMMANDCLASS_DELETE
                    Me.Adapter.DeleteCommand = MetaQuery.GetSPCmdByMeta(mq)
                Case Base.IdRepository.METALIST_METACOMMANDCLASS_UPDATE
                    Me.Adapter.UpdateCommand = MetaQuery.GetSPCmdByMeta(mq)
                Case Base.IdRepository.METALIST_METACOMMANDCLASS_CREATE
                    Me.Adapter.InsertCommand = MetaQuery.GetSPCmdByMeta(mq)
                Case Base.IdRepository.METALIST_METACOMMANDCLASS_CUD
                    Me.BuildCUDCommands(mq)
            End Select
        Next
        'By design On assume que le insert et le update sont pareil si les SP sont pas la.
        'de plus ca nous sauve un acces BD si pareil...

        'Dans les Metaquery, la classe Create assigne le InsertCommand et la Update le UpdateCommand
        'Si un des 2 est absent on l'assigne de l'autre.
        If Me.Adapter.UpdateCommand Is Nothing AndAlso Me.Adapter.InsertCommand IsNot Nothing Then
            'Ici Clone est utilisé a cause que je veux une nouvelle objet de la command. Je ne veux pas la référence
            'Les paramètres sont partagé si on ne fait pas de clone
            Me.Adapter.UpdateCommand = Me.Adapter.InsertCommand.Clone
        End If
        If Me.Adapter.InsertCommand Is Nothing AndAlso Me.Adapter.UpdateCommand IsNot Nothing Then
            'Ici Clone est utilisé a cause que je veux une nouvelle objet de la command. Je ne veux pas la référence
            'Les paramètres sont partagé si on ne fait pas de clone
            Me.Adapter.InsertCommand = Me.Adapter.UpdateCommand.Clone
        End If
    End Sub
    ''' <summary>
    ''' Use by the Adapter during an Insert/Update/Delete
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks>Use to modified the row or parameter during the execution</remarks>
    Private Sub RowUpdating(ByVal sender As Object, ByVal e As SqlRowUpdatingEventArgs)
        RaiseEvent OnRowUpdating(sender, e)
    End Sub
    ''' <summary>
    ''' Use by the Adapter after an Insert/Update/Delete
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub RowUpdated(ByVal sender As Object, ByVal e As SqlRowUpdatedEventArgs)
        ' Conditionally execute this code block on inserts and update.
        'If Not e.Status = UpdateStatus.ErrorsOccurred Then
        '    If e.StatementType = StatementType.Update Or e.StatementType = StatementType.Insert Then
        '        Dim recordVersion As Byte() = Me.MetaCommand.RefetchRowVersion(CType(e.Row(Me.MetaCommand.GUIDField), Guid))
        '        If recordVersion IsNot Nothing Then
        '            If e.Status = UpdateStatus.Continue Then
        '                e.Row.Table.Columns(Me.MetaCommand.RecordVersionField).ReadOnly = False
        '                e.Row(Me.MetaCommand.RecordVersionField) = recordVersion
        '            End If
        '        End If
        '    End If
        'End If
        RaiseEvent OnRowUpdated(sender, e)
    End Sub
#End Region
#End Region
#Region " IDisposable Support "
    ' IDisposable
    Private disposedValue As Boolean = False        ' To detect redundant calls
    Protected Overloads Sub Dispose(ByVal disposing As Boolean)
        MyBase.Dispose(disposing)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: free other state (managed objects).
                'RemoveHandler Me.Adapter.RowUpdated, AddressOf RowUpdated
                'RemoveHandler Me.Adapter.RowUpdating, AddressOf RowUpdating
                Me.command.Dispose()
                If _metaData IsNot Nothing Then
                    Me._metaData.Dispose()
                End If

                Me._metaCommand.Dispose()
                '
                Me.command = Nothing
                Me._metaData = Nothing
                Me._metaCommand = Nothing
            End If
            ' TODO: free your own state (unmanaged objects).
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Overloads Sub Dispose()
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class
